<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>SpellTrainer</title>
<meta http-equiv="Content-Type" content="Text/HTML; CharSet=ISO-8859-1">
<meta name="Author" content="Vasily Zakharov (vmzakhar@gmail.com)">
<link rel="Shortcut Icon" href="favicon.ico">
<style type="text/css">
html, body {
	margin: 0;
	padding: 0;
	font-family: Verdana, Arial, Helvetica, sans-serif;
}

p, td {
	font-size: 13px;
	line-height: 1.4;
	margin: 2ex 0;
	padding: 0;
}

.bold {
	font-weight: bold;
}

.light {
	font-weight: normal;
}

.right {
	text-align: right;
}

.welcome {
	color: black;
	background-color: white;
}

.ready {
	color: white;
	background-color: black;
}

.attack {
	color: green;
	background-color: black;
}

.defence {
	color: white;
	background-color: maroon;
}

.flash {
	color: white;
	background-color: white;
}

.example {
	font-weight: bold;
	padding: 3px;
}

table.visible {
	display: table;
}

td.visible {
	display: table-cell;
}

.invisible {
	display: none;
}

td.menu {
	white-space: nowrap;
	font-size: 70%;
	font-weight: bold;
	color: black;
	padding: 0 2px;
}

td.column {
	vertical-align: top;
	padding: 2px 2px 5px 0px;
}

td.spell, td.category {
	white-space: nowrap;
	font-size: 60%;
	color: black;
	padding-left: 3px;
}

td.category {
	font-weight: bold;
	padding-top: 5px;
}

td.padleft {
	padding-left: 6px;
}

button.menu {
	height: 42px;
	font-weight: bold;
	line-height: normal;
}

.input {
	border: 1px inset gray;
}

span.input {
	padding: 1px;
}

input.input {
	width: 4em;
}

input.menu {
	font-size: 100%;
	padding: 1px;
}

input.checkbox {
	border: 0;
	margin: 0;
}

img.logo {
	background-color: white;
	border: 1px solid black;
}

img.valid {
	width: 88px;
	height: 31px;
	border: 0;
}

#headerBlockID, #selectionBlockID, #footerBlockID {
	color: black;
	background-color: silver;
	border: 1px solid black;
	height: 42px;
}

#headerTable {
	width: 100%;
}

#logoBlockID {
	padding-left: 4px;
}

#headerTitle {
	text-align: center;
	color: black;
	font-weight: bold;
	padding: 0 2px 0 6px;
}

#menuPadding {
	width: 100%;
	text-align: center;
}

#noscriptTitle, #welcomeTitle {
	margin: 0;
	padding: 0;
	white-space: nowrap;
}

#loadingBlockID, #mainBlockID, #infoBlockID, #welcomeBlockID, #castBlockID {
	width: 100%;
	height: 100%;
}

#infoBlockID, #welcomeBlockID {
	color: black;
	text-align: center;
	white-space: nowrap;
}

#speedScrollID {
	width: 200px;
	height: 30px;
	overflow: scroll;
}

#speedFill {
	width: 1000px;
	height: 0;
}

#speedControlID {
	width: 2em;
}

#fontFamilyControlID {
	width: 5em;
}

#castBlockID {
	text-align: center;
	font-weight: bold;
}

#bigStartButton {
	font-size: 300%;
	font-weight: bold;
	cursor: pointer;
	margin: 0.5ex 0;
	padding: 0.5ex 0.5em;
}

#loggerBlockID {
	height: 38px;
	font-size: 50%;
	overflow: auto;
}
</style>
<!--[if IE]>
<style type="text/css">
table.visible, td.visible {
	display: block;
}

input.checkbox {
	margin-left: -3px;
}

span.input {
	padding-top: 2px;
}

#speedScrollID {
	overflow-x: scroll;
	overflow-y: hidden;
}

#bigStartButton {
	margin: 1.5ex 0;
	padding: 0.1ex 0;
}
</style>
<![endif]-->
<script type="text/javascript">
var LEVELS = ['Simpla', 'Maxima', 'Ultima'];
var DEFAULT_FONT_NAME = 'Verdana,Arial,Helvetica,sans-serif';
var MIN_FONT_SIZE = 20;
var RULE_OF_N_SPELLS = 3;
var COOKIE_NAME = 'SpellTrainer';
var GET_READY = 'Get ready!';
var NO_SPELLS = 'select spells';

var SPELLS = [
    [[null, null, [
      ['Protego', [1.0, 2.4, 7.7], [
        ['Impedimenta', [1.4, 2.8, 2.6]],
        ['Tarantallegra', [1.6, 3.2, 6.7]],
        ['Silencio', [1.1, 2.4, 4.6]]
      ]],
      ['Defendo', [1.2, 2.9, 6.1], [
        ['Rictusempra', [1.3, 1.8, 3.8]],
        ['Incarcero', [1.1, 2.4, 2.7]],
        ['Mento Menores', [3.3, 3.5, 1.6]],
        ['Petrificus Totalus', [null, 3.2, null]]
      ]],
      ['Enervate', [1.1, 2.4, 2.7], [
        ['Stupefy', [1.2, 2.4, 2.2]],
        ['Achelitus', [2.1, 3.0, 7.3]],
        ['Maledicero', [2.7, 4.7, 8.3]]
      ]],
      ['VIP', null, [
        ['Incendio', [2.3, 2.7, 5.5], 'Deluvium*'],
        ['Deluvium', [2.6, 2.9, 5.8], 'Incendio*'],
        ['Tormentio', [2.0, 3.1, 6.0], 'Floridus*'],
        ['Floridus', [3.3, 4.1, 6.2], true],
        ['Conjunctivitus', [1.5, 2.5, 4.6], 'Sensus Videndi*'],
        ['Sensus Videndi', [1.1, 2.5, 7.6], true],
        ['Expelliarmus', [null, 3.1, null], 'Para Bellum*'],
        ['Para Bellum', [null, 4.2, null], true]
    ]]]]],
    [[null, null, [
      ['Unforgivable', null, [
        ['Imperio', [2, 2, 2] , 'Interfere'],
        ['Crucio', [2, 2, 2], 'Interfere'],
        ['Avada Kedavra', [2, null, null]]
      ]],
      ['Others', null, [
        ['Metatrepo', [2, 2, 2], 'Metaiono Metatrepo'],
        ['Metaiono Metatrepo', [2, 2, 2], true],
        ['Sectumsempra', [2, 2, 2]], // ToDo: Protego Ultima blocks Simpla and Maxima?
        ['Exitus Vitalis', [2, null, null]],
        ['Laesa Cordis', [null, 2, 2], 'Fibrillacium Sanguis*'],
        ['Fibrillacium Sanguis', [null, 2, 2], true]
      ]],
      ['1898 VIP', null, [
        ['Glacialis', [2, 3, 4], 'Condeliquesco*'],
        ['Condeliquesco', [2, 3, 4], 'Glacialis*']
    ]]]]],
    [['Special', null, [
        ['Expecto Patronum', 2],
        ['Speculi Verto', 2],
        ['Fuga Tempero', 2],
        ['Flexio Fluctus Aeris', 2, 'Conversio Fluctus Aeris'],
        ['Conversio Fluctus Aeris', 2, true],
        ['Fidele et Fortis', 2],
        ['Furnunculus', 2, 'Formositas'],
        ['Formositas', 2, true]
    ]],
    ['Interfere', 2, [
        ['Silencium', 2, 'Interfere'],
        ['Morpheus', 2, 'Interfere'],
        ['Verbositas', 2, 'Interfere'],
        ['Amata Sententia', 2, 'Interfere'],
        ['Plausus', 2, 'Interfere'],
        ['Haesitantia Linguae', 2, 'Interfere'],
        ['Clato Verato Nicto', 2, 'Interfere'],
        ['Censorio', 2, 'Interfere'],
        ['Albaversa', 2, 'Interfere']
    ]]]];

function debug(message) {
    logger.innerHTML += new Date().toLocaleTimeString() + '&nbsp;&nbsp;' + message + '<br>';
    logger.scrollTop = logger.scrollHeight;
}

function getStyle(selector) {
    var rules = document.styleSheets[0].cssRules || document.styleSheets[0].rules; // Tries both ways to access CSS
    for (var i = 0, rule; rule = rules[i++];) {
        if (rule.selectorText === selector) {
            return rule.style;
        }
    }
}

function baseSpellName(spell) {
    baseIndex = spell.name.indexOf(' ');
    return baseIndex >= 0 ? spell.name.slice(0, baseIndex) : spell.name;
}

function SpellGenerator() {
    this.spells = [];
    this.previousSpells = [];
    this.next = function() {
        if (!this.spells.length) {
            return null;
        }
        newSpell:
        while (true) {
            spell = this.spells[Math.floor(Math.random() * this.spells.length)];
            baseSpell = baseSpellName(spell);
            if (this.spells.length >= RULE_OF_N_SPELLS) {
                checkRule = true;
                if (this.spells.length <= LEVELS.length * (RULE_OF_N_SPELLS - 1)) {
                    bases = [];
                    nextSpellCheck:
                    for (var i = 0, check; check = this.spells[i++];) {
                        check = baseSpellName(check);
                        for (var j = 0, base; base = bases[j++];) {
                            if (check === base) {
                                continue nextSpellCheck;
                            }
                        }
                        bases.push(check);
                    }
                    if (bases.length < RULE_OF_N_SPELLS) {
                        checkRule = false;
                    }
                }
                if (checkRule) {
                    for (var i = 0, previousSpell; previousSpell = this.previousSpells[i++];) {
                        if (previousSpell === baseSpell) {
                            continue newSpell;
                        }
                    }
                }
            }
            this.previousSpells = this.previousSpells.concat([baseSpell]).slice(-RULE_OF_N_SPELLS + 1);
            return spell;
        }
    }
}

function toggleSelectionBlock() {
    //debug('toggleSelectionBlock');
    if (selectionBlock.className === 'invisible') {
        selectionBlock.className = 'visible';
        selectionButton.innerHTML = 'Select Spells<br>&uarr; &uarr; &uarr;';
    } else {
        selectionBlock.className = 'invisible';
        selectionButton.innerHTML = 'Select Spells<br>&darr; &darr; &darr;';
    }
    setFont();
}

function setMode(element) {
    //debug('setMode');
    if (!attackControl.checked && !defenceControl.checked) {
        (element || attackControl).checked = true;
    }
}

function validateFactor(element) {
    if (!element) {
        return;
    }
    //debug('validateFactor');
    element.value = Math.round((parseFloat(element.value) || 1) * 10) / 10;
}

function setSpellControl(spell, value) {
    if (spell) {
        if (spell.control) {
            spell.control.checked = value;
        }
        for (var i = 0, subSpell; subSpell = spell.inCategory[i++];) {
            setSpellControl(subSpell, value);
        }
    }
}

function checkSpellControl(spell, value) {
    if (!spell) {
        return false;
    }
    if (!spell.control) {
        return true;
    }
    if (spell.control.checked !== value) {
        return false;
    }
    for (var i = 0, subSpell; subSpell = spell.inCategory[i++];) {
        if (!checkSpellControl(subSpell, value)) {
            return false;
        }
    }
    return true;
}

function setCategoryControl(spell, value) {
    if (spell && spell.category && spell.category.control) {
        for (var i = 0, subSpell; subSpell = spell.category.inCategory[i++];) {
            if (!checkSpellControl(subSpell, value)) {
                return;
            }
        }
        spell.category.control.checked = value;
        setCategoryControl(spell.category, value);
    }
}

function setSpells(element) {
    //debug('setSpells');
    if (element) {
        setSpellControl(element.spell, element.checked);
        setCategoryControl(element.spell, element.checked);
    }
    if (castBlock.className === 'invisible') {
        return;
    }
    attackGenerator.spells = [];
    defenceGenerator.spells = [];
    for (var i = 0, spell; spell = attackSpells[i++];) {
        if (spell.control.checked) {
            attackGenerator.spells.push(spell);
            if (spell.shield) {
                defenceGenerator.spells.push(spell);
            }
        }
    }
    setFont();
}

function castSpell() {
    var isAttack = (attackControl.checked && (!defenceControl.checked || Math.random() >= 0.5));
    var spell = (isAttack ? attackGenerator : defenceGenerator).next();
    var spellName = (spell ? spell.name : NO_SPELLS);
    var delay = spellDelay * (spell ? isAttack ? spell.factorControl.value : spell.shield.factorControl.value : 2);
    debug((isAttack ? 'Attack' : 'Defence') + ': ' + spellName + (!spell || isAttack ? '' : ' / ' + spell.shield.name) + ' (' + delay / 1000 + ')');
    castBlock.className = (isAttack ? 'attack' : 'defence');
    castBlock.innerHTML = spellName;
    timeout = setTimeout('hideSpell()', delay);
}

function hideSpell() {
    castBlock.className = 'flash';
    timeout = setTimeout('castSpell()', 100);
}

function setColors() {
    //debug('setColors');
    function doSetColors(style, field, display, control, defaultValue) {
        try { // Throws exception on IE if color value is incorrect
            style[field] = display.style.backgroundColor = control.value;
        } catch(err) {
            style[field] = display.style.backgroundColor = defaultValue;
        }
    }
    doSetColors(attackStyle, 'color', attackForegroundDisplay, attackForegroundControl, 'gray');
    doSetColors(attackStyle, 'backgroundColor', attackBackgroundDisplay, attackBackgroundControl, 'white');
    doSetColors(defenceStyle, 'color', defenceForegroundDisplay, defenceForegroundControl, 'gray');
    doSetColors(defenceStyle, 'backgroundColor', defenceBackgroundDisplay, defenceBackgroundControl, 'black');
}

function setSpeed() {
    //debug('setSpeed');
    speedScroll.scrollLeft = (speedScroll.scrollWidth - speedScroll.offsetWidth) * (speedControl.value - 1) / 9;
    adjustSpeed(); // calls saveConfig(), we must be sure it's called
}

function adjustSpeed() {
    speedControl.value = 1 + Math.round(speedScroll.scrollLeft * 9 / (speedScroll.scrollWidth - speedScroll.offsetWidth));
    //debug('adjustSpeed ' + speedControl.value);
    spellDelay = 3000 / speedControl.value;
    saveConfig();
}

function resize() {
    //debug('resize');
    castBlock.style.fontSize = MIN_FONT_SIZE;
    castBlock.style.fontSize = Math.floor(Math.min(castBlock.offsetHeight * 0.7, castBlock.offsetWidth * aspect)) + 'px';
}

function setFont() {
    if (castBlock.className === 'invisible') {
        return;
    }
    //debug('setFont');
    var savedClass = castBlock.className;
    var savedContent = castBlock.innerHTML;
    var spells = attackGenerator.spells.concat(defenceGenerator.spells, [GET_READY, NO_SPELLS]);
    castBlock.className = 'flash';
    castBlock.style.fontFamily = fontFamilyControl.value + ',' + DEFAULT_FONT_NAME;
    castBlock.style.fontWeight = fontBoldControl.checked ? 'bold' : 'normal';
    castBlock.style.fontStyle = fontItalicControl.checked ? 'italic' : 'normal';
    castBlock.style.fontSize = MIN_FONT_SIZE;
    castBlock.innerHTML = '<span id="test"><\/span>';
    var test = document.getElementById('test');
    aspect = 1000;
    for (var i = 0, spell; spell = spells[i++];) {
        test.innerHTML = (spell.name || spell);
        aspect = Math.min(aspect, test.offsetHeight / test.offsetWidth);
    }
    aspect *= 0.7;
    resize();
    castBlock.innerHTML = savedContent;
    castBlock.className = savedClass;
}

function start() {
    //debug('start');
    startButton.innerHTML = 'Stop';
    startButton.onclick = stop;
    welcomeBlock.className = 'invisible';
    castBlock.className = 'flash';
    setSpells();
    castBlock.innerHTML = GET_READY;
    body.onresize = onresize = resize;
    castBlock.className = 'ready';
    timeout = setTimeout('hideSpell()', spellDelay);
}

function stop() {
    //debug('stop');
    clearTimeout(timeout);
    startButton.innerHTML = 'Start!';
    startButton.onclick = start;
    welcomeBlock.className = 'visible';
    castBlock.className = 'invisible';
    body.onresize = onresize = null;
}

function Parameter(control, defaultValue) {
    this.control = control;
    this.defaultValue = defaultValue;
    this.isBoolean = (typeof this.defaultValue === 'boolean');
    this.isNumber = (typeof this.defaultValue === 'number');
    this.isInteger = (this.isNumber && this.defaultValue % 1 == 0);
    this.isFloat = (this.isNumber && !this.isInteger);

    this.get = function() {
        return (this.isBoolean ? this.control.checked : this.control.value);
    }

    this.set = function(value) {
        if (this.isBoolean) {
            this.control.checked = (value === true || value === 'true');
        } else {
            this.control.value = (this.isInteger ? parseInt(value) : this.isFloat ? parseFloat(value) : value);
        }
    }
}

function configureParameter(parameter, defaultValue) {
    var control = document.getElementById(parameter + 'ControlID');
    config[parameter] = new Parameter(control, defaultValue);
    return this[parameter + 'Control'] = control; // add to global namespace
}

function saveConfig() {
    //debug('saveConfig');
    var items = [];
    for (var name in config) {
        var parameter = config[name];
        var value = parameter.get();
        if (value != parameter.defaultValue) {
            items.push(escape(name) + '=' + escape(value));
        }
    }
    var date = new Date();
    date.setTime(date.getTime() + (items ? 365 : -1) * 24 * 60 * 60 * 1000); // 1 year
    document.cookie = COOKIE_NAME + '=' + escape(items.join(';')) + '; expires=' + date.toGMTString() + '; path=' + document.location.pathname;
}

function loadConfig(useCookie) {
    //debug('loadConfig(' + useCookie + ')');
    var fromCookie = {};
    if (useCookie) {
        var items = [];
        var lookFor = COOKIE_NAME + '=';
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = cookies[i].split(' ').join('');
            if (cookie.indexOf(lookFor) == 0) {
                items = unescape(cookie.substring(lookFor.length)).split(';');
                break;
            }
        }
        for (var i = 0; i < items.length; i++) {
            var item = items[i].split('=');
            if (item.length == 2) {
                fromCookie[unescape(item[0].split(' ').join(''))] = unescape(item[1].split(' ').join(''));
            }
        }
    }
    for (var name in config) {
        var parameter = config[name];
        parameter.set((name in fromCookie) ? fromCookie[name] : parameter.defaultValue);
    }
    setSpells();
    setColors();
    setSpeed(); // calls adjustSpeed() and saveConfig()
}

function Spell(name, control, factorControl, shield, category) {
    this.setShield = function(shield) {
        this.shield = (shield == true || this.isCategory ? null : shield ? shield : (this.category && this.category.isShield) ? this.category : null);
        if (this.shield && typeof this.shield === 'object') {
            if (!this.shield.isShield) {
                this.shield.isShield = true;
                shieldSpells.push(this.shield);
            }
            this.shield.attacks.push(this);
        }
    }
    this.name = name;
    this.control = control;
    if (control) {
        control.spell = this;
    }
    this.factorControl = factorControl;
    this.category = category;
    this.isCategory = (typeof shield === 'object');
    this.isSpell = Boolean(factorControl);
    this.isAttack = Boolean(this.isSpell && !this.isCategory && control);
    this.isShield = Boolean(this.isSpell && !this.isAttack); // can be also set to true on second pass
    this.setShield(shield);
    this.attacks = [];
    this.inCategory = [];
    if (this.isSpell) {
        allSpells.push(this);
    }
    if (this.isAttack) {
        attackSpells.push(this);
    }
    if (this.isShield) {
        shieldSpells.push(this);
    }
    if (this.category) {
        category.inCategory.push(this);
    }
}

function createSpellBlock(table, data, category, on) {
    var spellName = data[0];
    var factors = data[1];
    var shield = data[2];
    var columns;
    var d = data;
    var f;
    while (true) {
        f = d[1];
        if (f && typeof f === 'object' && f.length) {
            columns = LEVELS;
            break;
        }
        d = d[2];
        if (d && typeof d === 'object' && d[0]) {
            d = d[0];
        } else {
            columns = [''];
            break;
        }
    }
    var minLevel = (factors ? columns.length : 1);
    if (factors && columns.length > 1) {
        var found = false;
        for (var i = 0; i < factors.length; i++) {
            if (factors[i]) {
                if (found) {
                    minLevel = i;
                    break;
                } else {
                    found = true;
                }
            }
        }
    }
    var className = (typeof shield === 'object') ? 'category' : 'spell';
    var row = table.insertRow(-1);
    var ret = [];
    var cell;
    var factor;
    for (var i = 0; i < columns.length; i++) {
        var column = columns[i];
        factor = (factors && typeof factors === 'object' ? factors[i] : factors);
        cell = row.insertCell(-1);
        cell.className = className + ' padleft';
        var control = null;
        if (shield != true && (!factors || factor)) {
            cell.innerHTML = '<input class="checkbox" type="checkbox" onclick="setSpells(this); saveConfig()">';
            control = cell.firstChild;
        }
        cell = row.insertCell(-1);
        cell.className = className;
        var factorControl = null;
        if (!spellName) {
            cell.innerHTML = column;
        } else if (factor) {
            cell.innerHTML = '<input class="menu input" type="text" maxlength="4" onchange="validateFactor(this); saveConfig()">';
            factorControl = cell.firstChild;
        }
        if (control || factorControl) {
            var fullSpellName = (!spellName ? column : (column && i >= minLevel) ? (spellName + ' ' + column) : spellName);
            var fullShield = (typeof shield === 'string') ? shield.replace('*', (column && i >= minLevel) ? (' ' + column) : '') : shield;
            var n = 0;
            while (fullSpellName + (n || '') in config) {
                n++;
            }
            fullSpellName += (n || '');
            if (control) {
                config[fullSpellName] = new Parameter(control, Boolean(on && on[i]));
            }
            if (factorControl) {
                config[fullSpellName + ' Factor'] = new Parameter(factorControl, factor);
            }
            spell = new Spell(fullSpellName, control, factorControl, fullShield, category ? category[i] : null);
            if (typeof shield === 'string') {
                spellsNeedingShields.push(spell);
            }
            ret.push(spell);
        } else {
            ret.push(null);
        }
    }
    cell = row.insertCell(-1);
    cell.className = className;
    cell.innerHTML = spellName;
    return ret;
}

function createSpellSection(columnBlock, section, superSectionObjects, on) {
    var sectionObjects = createSpellBlock(columnBlock, section, superSectionObjects, on);
    var subSections = section[2];
    if (subSections && typeof subSections === 'object') {
        for (var i = 0, subSection; subSection = subSections[i++];) {
            createSpellSection(columnBlock, subSection, sectionObjects, on);
        }
    }
}

function main() {
    logger = document.getElementById('loggerBlockID');
    //debug('main');
    // HTML references
    attackStyle = getStyle('.attack');
    defenceStyle = getStyle('.defence');
    body = document.getElementsByTagName('body')[0];
    selectionBlock = document.getElementById('selectionBlockID');
    welcomeBlock = document.getElementById('welcomeBlockID');
    castBlock = document.getElementById('castBlockID');
    startButton = document.getElementById('startButtonID');
    selectionButton = document.getElementById('selectionButtonID');
    resetButton = document.getElementById('resetButtonID');
    spellSelector = document.getElementById('spellSelectorID');
    speedScroll = document.getElementById('speedScrollID');
    attackForegroundDisplay = document.getElementById('attackForegroundDisplayID');
    attackBackgroundDisplay = document.getElementById('attackBackgroundDisplayID');
    defenceForegroundDisplay = document.getElementById('defenceForegroundDisplayID');
    defenceBackgroundDisplay = document.getElementById('defenceBackgroundDisplayID');
    // Initial settings
    timeout = null;
    stop();
    toggleSelectionBlock();
    attackGenerator = new SpellGenerator();
    defenceGenerator = new SpellGenerator();
    if (window.opera) {
        getStyle('button.menu')['padding-top'] = '11px';
        selectionButton.style.lineHeight = 1.3;
        selectionButton.style.paddingTop = '5px';
    }
    // Create config
    config = {};
    configureParameter('speed', 3);
    configureParameter('attack', true);
    configureParameter('defence', true);
    configureParameter('attackForeground', 'green');
    configureParameter('attackBackground', 'black');
    configureParameter('defenceForeground', 'white');
    configureParameter('defenceBackground', 'maroon');
    configureParameter('fontFamily', DEFAULT_FONT_NAME.slice(0, DEFAULT_FONT_NAME.indexOf(',')));
    configureParameter('fontBold', true);
    configureParameter('fontItalic', false);
    // Configure spell selector
    allSpells = [];
    attackSpells = [];
    shieldSpells = [];
    spellsNeedingShields = [];
    spellSelector.deleteCell(0);
    for (var i = 0, column; column = SPELLS[i++];) {
        var cell = spellSelector.insertCell(-1);
        cell.className = 'column';
        cell.innerHTML = '<table cellspacing="0" cellpadding="0"><\/table>';
        var columnBlock = spellSelector.lastChild.firstChild;
        for (var j = 0, section; section = column[j++];) {
            createSpellSection(columnBlock, section, null, (i > 1) ? null : [true, true, false]);
        }
    }
    // Link remaining shields
    for (var i = 0, spell; spell = spellsNeedingShields[i++];) {
        for (var j = 0, shieldSpell; shieldSpell = allSpells[j++];) {
            if (shieldSpell.name == spell.shield) {
                spell.setShield(shieldSpell);
                break;
            }
        }
        if (!shieldSpell) {
            throw 'No shield found for ' + spell.name + ' ( ' + spell.shield + ')';
        }
    }
    delete allSpells;
    delete spellsNeedingShields;
    // Show main page
    document.getElementById('loadingBlockID').className = 'invisible';
    document.getElementById('mainBlockID').className = 'visible';
    loadConfig(true);
    speedScroll.style.height = speedScroll.offsetHeight - speedScroll.clientHeight + 'px';
    speedScroll.onscroll = adjustSpeed;
    document.getElementsByTagName('body')[0].removeAttribute('onload'); // Don't even try to work after saving modified page
}
</script>
</head>
<body onload="main()">
  <table id="loadingBlockID" class="visible" cellspacing="0" cellpadding="0">
    <tr>
      <td id="infoBlockID">
        <h1 id="noscriptTitle">SpellTrainer&nbsp;v1.02</h1>
        <p>[<a href="http://jolaf.jnm.ru/spelltrainer/">Latest&nbsp;version</a>] [<a href="http://jolaf.jnm.ru/spelltrainer/spelltrainer.zip">Download</a>] [<a href="http://code.google.com/p/spelltrainer/">Project&nbsp;page</a>] [<a href="mailto:vmzakhar@gmail.com?Subject=SpellTrainer">Contact&nbsp;mail</a>]</p>
        <p><img class="logo" src="wand.png" alt=""></p>
        <p>This software is created to train players in spellcasting for <a href="http://hp-ekb.ru">Hogwarts&nbsp;Seasons&nbsp;LARPs</a>.</p>
        <br>
        <script type="text/javascript">document.write('<p class="bold">...loading, please wait...<\/p>')</script>
        <noscript>
          <p class="bold">This page requires <a href="http://mozilla.org/js/">JavaScript</a> to operate.</p>
          <p class="bold">Please enable JavaScript in your browser and reload the page.</p>
        </noscript>
        <br>
        <p>If you experience problems or want to report a bug &ndash; please mail to <a href="mailto:vmzakhar@gmail.com?Subject=SpellTrainer">vmzakhar@gmail.com</a>.</p>
      </td>
    </tr>
  </table>
  <table id="mainBlockID" class="invisible" cellspacing="0" cellpadding="0">
    <tr>
      <td id="headerBlockID">
        <table id="headerTable" cellspacing="0" cellpadding="0">
          <tr>
            <td id="logoBlockID"><img class="logo" src="wand.png" alt=""></td>
            <td id="headerTitle">SpellTrainer<br>v1.02</td>
            <td id="menuPadding" class="menu"><button id="selectionButtonID" class="menu" type="button" onclick="toggleSelectionBlock()"></button></td>
            <td class="menu"><button id="startButtonID" class="menu" type="button" onclick="start()"></button></td>
            <td>
              <table align="right" cellspacing="0" cellpadding="0">
                <tr>
                  <td>
                    <table cellspacing="0" cellpadding="0">
                      <tr>
                        <td class="menu padleft">Tempo <input id="speedControlID" class="menu input" type="text" maxlength="2" onchange="setSpeed(); saveConfig()"> <span class="light">1 slowest, 10 fastest</span></td>
                        <td class="menu padleft right">Attack</td>
                        <td class="menu"><input id="attackControlID" class="checkbox" type="checkbox" onclick="setMode(this); saveConfig()"></td>
                        <td class="menu"><input id="attackForegroundControlID" class="menu input" type="text" maxlength="20" onchange="setColors(); saveConfig()"><span id="attackForegroundDisplayID" class="input">&nbsp;&nbsp;&nbsp;</span> on <span id="attackBackgroundDisplayID" class="input">&nbsp;&nbsp;&nbsp;</span><input id="attackBackgroundControlID" class="menu input" type="text" maxlength="20" onchange="setColors(); saveConfig()"></td>
                        <td colspan="4" class="menu padleft">Font <input id="fontFamilyControlID" class="menu input" type="text" maxlength="32" onchange="setFont(); saveConfig()"></td>
                      </tr>
                      <tr>
                        <td class="menu padleft"><div id="speedScrollID"><div id="speedFill">&nbsp;</div></div></td>
                        <td class="menu padleft right">Defence</td>
                        <td class="menu"><input id="defenceControlID" class="checkbox" type="checkbox" onclick="setMode(this); saveConfig()"></td>
                        <td class="menu"><input id="defenceForegroundControlID" class="menu input" type="text" maxlength="20" onchange="setColors(); saveConfig()"><span id="defenceForegroundDisplayID" class="input">&nbsp;&nbsp;&nbsp;</span> on <span id="defenceBackgroundDisplayID" class="input">&nbsp;&nbsp;&nbsp;</span><input id="defenceBackgroundControlID" class="menu input" type="text" maxlength="20" onchange="setColors(); saveConfig()"></td>
                        <td class="menu padleft"><input id="fontBoldControlID" class="checkbox" type="checkbox" onclick="setFont(); saveConfig()"></td>
                        <td class="menu">bold</td>
                        <td class="menu"><input id="fontItalicControlID" class="checkbox" type="checkbox" onclick="setFont(); saveConfig()"></td>
                        <td class="menu">italic</td>
                      </tr>
                    </table>
                  </td>
                </tr>
              </table>
            </td>
            <td class="menu padleft"><button id="resetButtonID" class="menu" type="button" onclick="loadConfig(false)">Reset</button></td>
          </tr>
        </table>
      </td>
    </tr>
    <tr>
      <td id="selectionBlockID">
        <table id="selectionTable" cellspacing="0" cellpadding="0">
          <tr id="spellSelectorID"><td></td></tr>
        </table>
      </td>
    </tr>
    <tr>
      <td id="welcomeBlockID" class="visible">
        <h1 id="welcomeTitle">SpellTrainer&nbsp;v1.02</h1>
        <p>[<a href="http://jolaf.jnm.ru/spelltrainer/">Latest&nbsp;version</a>] [<a href="http://jolaf.jnm.ru/spelltrainer/spelltrainer.zip">Download</a>] [<a href="http://code.google.com/p/spelltrainer/">Project&nbsp;page</a>] [<a href="mailto:vmzakhar@gmail.com?Subject=SpellTrainer">Contact&nbsp;mail</a>]</p>
        <p><img class="logo" src="wand.png" alt=""></p>
        <p>This software is created to train players in spellcasting for <a href="http://hp-ekb.ru">Hogwarts&nbsp;Seasons&nbsp;LARPs</a>.</p>
        <p>Adjust the settings at the top bar as neccessary and press</p>
        <p><button id="bigStartButton" type="button" onclick="start()">Start!</button></p>
        <p class="bold">Click anywhere in the spell window to stop and return to this screen.</p>
        <p>When you see a spell written in <span class="example attack">&nbsp;attack colors&nbsp;</span> &ndash; cast it at your imaginary opponent.</p>
        <p>When you see a spell written in <span class="example defence">&nbsp;defence colors&nbsp;</span> &ndash; it means that spell<br>is cast by your opponent at you, so cast the respective shield.</p>
        <p>Start with a comfortable tempo, and then increase the pace as your casting skill improves.<br>You may adjust tempo factors for individual spells to give yourself more or less time for those spells.</p>
        <p>If you experience problems or want to report a bug &ndash; please mail to <a href="mailto:vmzakhar@gmail.com?Subject=SpellTrainer">vmzakhar@gmail.com</a>.</p>
        <p class="bold">Good luck!</p>
        <p><br><a href="http://validator.w3.org/check?uri=referer" title="Valid HTML 4.01 Transitional"><img class="valid" src="validhtml401.png" alt="Valid HTML 4.01 Transitional"></a> <a href="http://jigsaw.w3.org/css-validator/check/referer" title="Valid CSS 2.1"><img class="valid" src="validcss.gif" alt="Valid CSS 2.1"></a></p>
      </td>
      <td id="castBlockID" class="invisible" onclick="stop()"></td>
    </tr>
    <tr>
      <td id="footerBlockID">
        <div id="loggerBlockID"></div>
      </td>
    </tr>
  </table>
</body>
</html>
